「 Kata Containers 」源码走读 — virtcontainers/hypervisor
based on 3.0.0
Hypervisor
src/runtime/virtcontainers/hypervisor.go
Kata Containers 支持的 hypervisor 有 QEMU、Cloud Hypervisor、Firecracker、ACRN 以及 DragonBall,其中 DragonBall 是 Kata Containers 3.0 为新增的 runtime-rs 组件引入的内置 hypervisor,而 runtime-rs 的整体架构区别于当前的 runtime,不在此详读 DragonBall 实现。
目前,暂时走读 QEMU 实现,后续补充其他 hypervisor 实现。
1 | // qemu is an Hypervisor interface implementation for the Linux qemu hypervisor. |
1 | /* 前置说明 |
Hypervisor 中声明的 HypervisorConfig、setConfig、GetVirtioFsPid,fromGrpc、toGrpc,Save 和 Load 均为参数获取与赋值,无复杂逻辑,不作详述。
qmpSetup
初始化 QMP 服务
- 如果当前 QMP 服务已经就绪,则直接返回
- 启动 goroutine,处理 QMP 事件,如果为 GUEST_PANICKED 事件,并且指定了 [hypervisor].guest_memory_dump_path,则转储 VM 的内存信息
- 保存 sandbox 元数据信息
- 创建 [hypervisor].guest_memory_dump_path/<sandboxID>/state 目录(如果不存在)
- 将 <storage.PersistDriver.RunStoragePath>/<sandboxID> 目录下的内容拷贝至 [hypervisor].guest_memory_dump_path/<sandboxID>/state 目录中
- 将 hypervisor 的配置信息写入 [hypervisor].guest_memory_dump_path/<sandboxID>/hypervisor.conf 文件中
- 执行 qemu-system –version,获取 QEMU 的版本信息,写入 [hypervisor].guest_memory_dump_path/<sandboxID>/hypervisor.version 文件中
- 校验 [hypervisor].guest_memory_dump_path/<sandboxID> 目录空间是否为 VM 内存(静态 + 热添加)的两倍以上
- 向 QMP 服务发送 dump-guest-memory 命令,将 VM 中的内存内容转储到 [hypervisor].guest_memory_dump_path/<sandboxID>/vmcore-<currentTime>.elf 文件中,是否内存分页取决于 [hypervisor].guest_memory_dump_paging
- 保存 sandbox 元数据信息
- 启动 QMP 服务,监听 qmp.sock,校验 QEMU 版本是否大于 5.x
- 向 QMP 服务发送 qmp_capabilities 命令,从 capabilities negotiation 模式切换至 command 模式,命令无报错则视为 VM 处于正常运行状态
hotplugDevice
VM 设备热插拔
block
先调用 blockdev-add 命令是为了创建一个块设备,并将其配置为所需的类型、格式等。这个过程中,QEMU 会加载相应的块设备驱动程序,并为块设备分配所需的资源。然后调用 device_add 命令是为了将该块设备添加到 VM 中,使其成为 VM 的一部分。
- 调用 qmpSetup,初始化 QMP 服务
- 如果为热添加
- 如果 [hypervisor].block_device_driver 为 nvdimm,或者为 PMEM 设备,则向 QMP 服务发送 object-add 和 device_add 命令,为 VM 添加块设备;否则,向 QMP 服务发送 blockdev-add 命令,准备块设备
- 如果 [hypervisor].block_device_driver 为 virtio-blk 或 virtio-blk-ccw 时,会在 bridge 中新增设备信息维护;如果为 virtio-scsi 时,设备会添加到 scsi0.0 总线中
- 向 QMP 服务发送 device_add 命令,为 VM 添加块设备
- 如果为热移除
- 如果 [hypervisor].block_device_driver 为 virtio-blk 时,移除 bridge 中维护的设备信息
- 向 QMP 服务发送 device_del 和 blockdev-del 命令,移除 VM 中的指定块设备
CPU
- 调用 qmpSetup,初始化 QMP 服务
- 如果为热添加
- 如果当前 VM 的 CPU 数量与待热添加的 CPU 数量之和超出 [hypervisor].default_maxvcpus 限制,并不会不报错中断,而是热插至最大数量限制
- 向 QMP 服务发送 query-hotpluggable-cpus 命令,获得 host 上可插拔的 CPU 列表
- 遍历所有可插拔的 CPU,向 QMP 服务发送 device_add 命令,为 VM 添加未被使用的 CPU(如果 CPU 的 qom-path 不为空,则代表其正在使用中)
添加失败并不会报错,而是尝试其他 CPU,直至满足数量要求或者再无可用的 CPU
- 如果为热移除
- 只有热添加的 CPU 才可以热移除,因此需要校验期望热移除的 CPU 数量是否小于当前热添加的 CPU 数量
- 向 QMP 服务发送 device_del 命令,移除 VM 中最近添加的 CPU(即倒序移除)
VFIO
[hypervisor].hotplug_vfio_on_root_bus 决定是否允许 VFIO 设备在 root 总线上热插拔,默认为 true。VFIO 是一种用于虚拟化环境中的设备直通技术,它允许将物理设备直接分配给 VM,从而提高 VM 的性能和可靠性。然而,在桥接设备上进行 VFIO 设备的热插拔存在一些限制,特别是对于具有大型 PCI 条的设备。因此,通过将该选项设置为 true,可以在 root 总线上启用 VFIO 设备的热插拔,从而解决这些限制问题
- 调用 qmpSetup,初始化 QMP 服务
- 如果为热添加
- 如果启用 [hypervisor].hotplug_vfio_on_root_bus,则后续的设备添加操作会作用在 root 总线上,否则会作用在 bridge 上
- 向 QMP 服务发送 device_add 命令,为 VM 添加 VFIO-CCW、VFIO-PCI 或 VFIO-AP 设备
- 如果为热移除
- 如果未启用 [hypervisor].hotplug_vfio_on_root_bus,则移除 bridge 中维护的设备信息
- 向 QMP 服务发送 device_del 命令,移除 VM 中的指定 VFIO 设备
memory
先调用 object-add 命令是为了创建一个内存设备对象,并为其分配内存,以便后续使用。而后调用 device_add 命令是为了将该内存设备对象添加到 VM 中,使其成为 VM 的一部分,从而实现内存的热添加。
- 检验 VM protection 模式是否为 noneProtection,其他 VM protection 模式下均不支持内存热插拔特性
- 调用 qmpSetup,初始化 QMP 服务
- 仅支持热添加内存,不支持热移除
- 向 QMP 服务发送 query-memory-devices 命令,查询 VM 中所有的内存设备,用于生成下一个内存设备的 slot 序号
- 向 QMP 服务发送 object-add 和 device_add 命令,为 VM 添加内存设备
- 如果 VM 内核只支持通过探测接口热添加内存(通过内存设备的 probe 属性判断),则需要额外向 QMP 服务发送 query-memory-devices 命令,查询 VM 中最近的一个内存设备,回写其地址信息
endpoint
netdev_add 添加的是网络前端设备,而 device_add 添加的是一个完整的设备,其中包括前端设备和后端设备。在添加网络设备时,通常需要先添加一个网络前端设备,然后再将它连接到一个网络后端设备上。
- 调用 qmpSetup,初始化 QMP 服务
- 如果为热添加
- 向 QMP 服务发送 getfd 命令,分别获取 tap 设备的 VMFds 和 VhostFds 的信息
- bridge 中新增设备信息维护
- 向 QMP 服务发送 netdev_add 和 device_add 命令,为 VM 添加 PCI 或 CCW 类型([hypervisor].machine 为 s390-ccw-virtio 时)的网络设备
- 如果为热移除
- 移除 bridge 中维护的设备信息
- 向 QMP 服务发送 device_del 和 netdev_del 命令,移除 VM 中指定的网络设备
vhost-user
vhost-user 设备需要与 host 的网络堆栈进行通信,而 host 网络堆栈使用字符设备来管理网络连接。因此,要创建一个 vhost-user 设备,需要先创建一个字符设备,然后将其与 vhost-user 设备连接。
- 调用 qmpSetup,初始化 QMP 服务
- 如果为热添加,仅支持 vhost-user-blk-pci 类型的设备
- 向 QMP 服务发送 chardev-add 和 device_add 命令,为 VM 添加指定的 vhost-user 设备
- 如果为热移除
- 向 QMP 服务发送 device_del 和 chardev-remove 命令,移除 VM 中指定的 vhost-user 设备
CreateVM
准备创建 VM 所需的配置信息
- 根据 QEMU 实现的 hypervisor 配置项初始化对应架构下的 qemu,其中包含了 qemu-system(govmmQemu.Config)和 virtiofsd/nydusd(VirtiofsDaemon)进程的配置参数
StartVM
启动 VM
以当前用户组信息创建 <storage.PersistDriver.RunVMStoragePath>/<sandboxID> 目录(如果不存在)
如果启用 [hypervisor].enable_debug,则设置 qemuConfig.LogFile 为 <storage.PersistDriver.RunVMStoragePath>/<sandboxID>/qemu.log
如果未启用 [hypervisor].disable_selinux,则向 /proc/thread-self/attr/exec (如果其不存在,则为 /proc/self/task/<PID>/attr/exec)中写入 OCI spec.Process.SelinuxLabel 中声明的内容,VM 启动之后会重新置空
如果 [hypervisor].shared_fs 为 virtiofs-fs 或者 virtio-fs-nydus,则调用 VirtiofsDaemon 的 Start,启动 virtiofsd 进程,回写 virtiofsd PID 至 qemustate 中
构建 QEMU 进程的启动参数、执行命令的文件句柄、属性、标准输出等信息,执行 qemu-system 可执行文件,启动 qemu-system 进程。如果启用 [hypervisor].enable_debug 并且配置中指定了日志文件路径,则读取日志内容,追加错误信息
关停当前的 QMP 服务,执行类似于 qmpSetup 的流程,初始化 QMP 服务,无报错即视为 VM 处于正常运行状态
如果 VM 从模板启动
- 调用 qmpSetup,初始化 QMP 服务
- 向 QMP 服务发送 migrate-set-capabilities 命令,设置在迁移过程中忽略共享内存,避免数据的错误修改和不一致性
- 向 QMP 服务发送 migrate-incoming 命令,用于将迁移过来的 VM 恢复到 [factory].template_path/state 中
- 向 QMP 服务发送 query-migrate 命令,查询迁移进度,直至完成
如果启用 [hypervisor].enable_virtio_mem
- virtio-mem 设备后续会添加至 VM 的 root 总线中,获取地址和 bridge 等信息,后续执行 QMP 命令时传递
- 则向 QMP 服务发送 object-add 和 device_add 命令,为 VM 添加指定的 virio-mem 设备
如果 QMP 添加设备失败,且报错中包含 Cannot allocate memory,则需要执行 echo 1 > /proc/sys/vm/overcommit_memory 解决
StopVM
关闭 VM
- 调用 qmpSetup,初始化 QMP 服务
- 如果 disableVMShutdown 为 true(调用 agent 的 init 时会返回 disableVMShutdown,用作 StopVM 的入参),则调用 GetPids,获得所有相关的 PIDs,kill 掉其中的 QEMU 进程(即列表中索引为 0 的 PID);否则,则向 QMP 服务发送 quit 命令,关闭 QEMU 实例,关闭 VM
- 如果 [hypervisor].shared_fs 为 virtiofs-fs 或者 virtio-fs-nydus,调用 VirtiofsDaemon 的 Stop,关停 virtiofsd 服务
PauseVM
暂停 VM
- 调用 qmpSetup,初始化 QMP 服务
- 向 QMP 服务发送 stop 命令,暂停 VM
SaveVM
- 调用 qmpSetup,初始化 QMP 服务
- 如果 VM 启动后作为模板,则向 QMP 服务发送 migrate-set-capabilities 命令,设置 VM 在迁移过程中忽略共享内存,避免数据的错误修改和不一致性
- 向 QMP 服务发送 migrate 命令,将 VM 迁移到指定 [factory].template_path/state 中
- 向 QMP 服务发送 query-migrate 命令,查询迁移进度,直至完成
ResumeVM
恢复 VM
- 调用 qmpSetup,初始化 QMP 服务
- 向 QMP 服务发送 cont 命令,恢复 VM
AddDevice
向 VM 中添加设备
- 根据不同设备类型,初始化对应的设备对象 — govmmQemu.Device,追加到 qemuConfig.Devices 中
HotplugAddDevice
热添加指定设备至 VM 中
- 调用 hotplugDevice,热添加指定设备至 VM 中
HotplugRemoveDevice
热移除 VM 中的指定设备
- 调用 hotplugDevice,热移除 VM 中的指定设备
ResizeMemory
调整 VM 内存规格
- 调用 GetTotalMemoryMB,获取 VM 当前的内存
- 调用 qmpSetup,初始化 QMP 服务
- 如果启用 [hypervisor].enable_virtio_mem,向 QMP 服务发送 qom-set 命令,设置 virtiomem0 设备的 requested-size 属性值为待热添加的内存量(即期望的 VM 内存与 [hypervisor].default_memory 的差值),直接返回
virtio-mem 只需要将用于 host 和 guest 内存共享的 virtiomem0 设备内存扩大至预期大小即可,不需要返回内存设备对象,也不会调用 agent 通知内存上线 - 调用 HotplugAddDevice 或者 HotplugRemoveDevice,为 VM 调整内存规格,取决于 VM 当前内存是否大于预期 VM 内存大小
如果期望的 VM 内存超出了 [hypervisor].default_maxmemory 限制,也不会报错中断,而是热插至最大数量限制
ResizeVCPUs
调整 VM CPU 规格
- 调用 HotplugAddDevice 或者 HotplugRemoveDevice,为 VM 调整 CPU 规格,取决于 VM 当前 CPU 是否大于预期 VM CPU 大小
GetTotalMemoryMB
获取 VM 总内存
- 返回 [hypervisor].default_memory 和已热添加内存之和
GetVMConsole
获取 VM console 地址
- 返回 <storage.PersistDriver.RunVMStoragePath>/<sandboxID>/console.sock
Disconnect
断开 QMP 连接
- channel 关闭,重置 QMP 对象
Capabilities
获取 hypervisor 支持的特性
- 设置并返回 hypervisor 默认支持特性,包括块设备、设备多队列和文件系统共享特性支持
GetThreadIDs
获取 VM 中 CPU 的 threadID 信息
- 调用 qmpSetup,初始化 QMP 服务
- 向 QMP 服务发送 query-cpus-fast 命令,获取 VM 中所有 CPU 详细信息
- 遍历所有 CPU,返回其 CPU ID 和 threadID 的映射关系
Cleanup
hypervisor 相关资源清理
- 关闭 QEMU 所有相关的文件句柄
GetPids
获取 hypervisor 相关的 PID
- 读取 <storage.PersistDriver.RunVMStoragePath>/<sandboxID>/pid 文件内容,如果 virtiofsd 服务的 PID 不为空,则一并返回
Check
VM 状态检查
- 调用 qmpSetup,初始化 QMP 服务
- 向 QMP 服务发送 query-status 命令,查询并校验 VM 状态是否为 internal-error 或 guest-panicked
GenerateSocket
生成 host 和 guest 通信的 socket 地址
- 获取 /dev/vhost-vsock 设备的文件句柄
- 获取一个从 0x3(contextID 中 1 和 2 是内部预留的) 到 0xFFFFFFFF(2^32 - 1)范围内可用的 contextID
- 返回包含 vhost-vsock 设备的文件句柄、可用的 contextID 以及端口为 1024 的 VSock 对象
IsRateLimiterBuiltin
hypervisor 是否原生支持限速特性
- 返回 false,QEMU 未内置支持限速功能
VirtiofsDaemon
src/runtime/virtcontainers/virtiofsd.go
VirtiofsDaemon 是用于 host 与 guest 的文件共享的进程服务,实现包括 virtiofsd 以及蚂蚁社区提出的 nydusd。
目前,暂时走读 virtiofsd 实现,后续补充其他 virtiofsd 实现。
1 | // virtiofsd 进程启动参数中还有 |
Start
启动 virtiofsd 服务
- 检验 virtiofsd 服务相关参数是否为空以及
/run/kata-containers/shared/sandboxes/<containerID>/shared 路径是否存在 - 获取 <storage.PersistDriver.RunVMStoragePath>/<sandboxID>/vhost-fs.sock 的文件句柄,并将其权限设置为 root
这里区别于 QEMU 进程,QEMU 可以以非 root 运行,而 virtiofsd 暂不支持,参考 https://github.com/kata-containers/kata-containers/issues/2542 - 执行 virtiofsd 可执行文件,启动 virtiofsd 进程
- 启动 goroutine,如果 virtiofsd 程序退出,则调用 Hypervisor 的 StopVM,执行清理操作
- 返回 virtiofsd 进程 PID
Stop
关停 virtiofsd 服务
- kill 掉 virtiofsd 服务进程
- 移除 <storage.PersistDriver.RunVMStoragePath>/<sandboxID>/vhost-fs.sock 文件
Mount
将 rafs 格式文件挂载至 virtiofs 挂载点
virtiofsd 场景下暂未实现。
Umount
移除 virtiofs 挂载点下的 rafs 挂载文件
virtiofsd 场景下暂未实现。
「 Kata Containers 」源码走读 — virtcontainers/hypervisor